
#region Imported Namespaces
using System;
using System.Collections;
using System.Reflection;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;
using System.IO;
#endregion

namespace PHSRAG.WebControls
{
	#region Class PlaceHolderEx
	/// <summary>
	/// The out-of-the-box PlaceHolder control does not support the loading/saving of view state of its
	/// constitutent controls. The PlaceHolderEx class simply adds this functionality. The view state of all
	/// children are stored along with the view state of the PlaceHolder itself. Note that we recurse through
	/// the entire child hierarchy thereby allowing nesting to any depth.
	/// </summary>
	public class PlaceHolderEx : PlaceHolder
	{
		#region Delegates & Events
		/// <summary>
		/// The PlaceHolderEventHandler is a delegate used by clients of the PlaceHolderEx who wish to be informed
		/// when controls are restored.
		/// </summary>
		public delegate void PlaceHolderEventHandler(Control c);

		/// <summary>
		/// The ControlRestored is an event raised by the PlaceHolderEx when controls are restored.
		/// </summary>
		public event PlaceHolderEventHandler ControlRestored;
		#endregion

		#region Overrides
		/// <summary>
		/// The LoadViewState method first restores the structure and context of the entire constituent
		/// hierarchy and then that of the PlaceHolder itself.
		/// </summary>
		/// <param name="savedState">Contains streamed viewstate from which to load control states</param>
		protected override void LoadViewState(object savedState)
		{
			object[] viewState = (object[])savedState;
			Pair persistInfo = (Pair)viewState[0];
			foreach(Pair pair in (ArrayList)persistInfo.Second)
				RestoreChildStructure(pair, this);
			
			base.LoadViewState(viewState[1]);
		}

		/// <summary>
		/// The SaveViewState method first saves the structure and context of the entire constituent
		/// hierarhcy and then that of the PlaceHolder itself. Notice that an explicit test is made to
		/// ensure that we don't attempt to save save while in design mode.
		/// </summary>
		/// <returns>An array of two objects; the first contains the view state of children and the
		/// second contains the view state of this class itself.</returns>
		protected override object SaveViewState()
		{
			if (HttpContext.Current == null)	// No design-time support.
				return null;

			object[] viewState = new object[2];
			viewState[0] = PersistChildStructure(this, "C");
			viewState[1] = base.SaveViewState();
			return viewState;
		}
		#endregion

		#region Private Methods
		/// <summary>
		/// The PersistChildStructure method calls itself recursively to save the structure and context of
		/// the child hiearchy. Since UserControls may themselves contain other controls (which is typical),
		/// they are handled slightly differently than plain-old controls. UserControls are capable of saving
		/// state on their own.
        /// <remarks>We are simply saving the relative path of the user controls, since this is what we use
        /// to load the controls back in RestoreChildStructure.  We used to store the typename and path
        /// seperately, but there was a change in how .Net 2.0 reported the typename, it prepended some
        /// of the namespace to the control name.</remarks>
		/// </summary>
		/// <param name="control">The control whose state is to be persisted in the viewstate</param>
		/// <param name="controlCollectionName">Identifier of the control collection within which the state will be persisted</param>
		/// <returns>A pair of the view state of a control and a name which is assinged to it</returns>
		private Pair PersistChildStructure(Control control, string controlCollectionName)
		{
			string typeName;
            UserControl userControl = control as UserControl;
            if (userControl != null)
                typeName = "UC:" + userControl.AppRelativeVirtualPath;
            else
                typeName = "C:" + control.GetType().AssemblyQualifiedName;

			string persistedString = controlCollectionName + ";" + typeName + ";" + control.ID;

			ArrayList childPersistInfo = new ArrayList();
			if (userControl == null)	// Children of a UserControl need not be saved as they are recreated in Page.LoadControl.
			{
				for (int i = 0, count = control.Controls.Count; i < count; ++i)
				{
					Control child = control.Controls[i];
					Pair pair = PersistChildStructure(child, controlCollectionName);
					if (pair != null)
						childPersistInfo.Add(pair);
				}
			}

			return new Pair(persistedString, childPersistInfo);
		}

		/// <summary>
		/// The RestoreChildStructure mehtod restores the structure and context of a single control.
		/// </summary>
		/// <param name="persistInfo">A pair containing the view state of the control and its identifier.</param>
		/// <param name="parent">The control to whose collection the current control is to be added.</param>
		private void RestoreChildStructure(Pair persistInfo, Control parent)
		{
			string[] persistedString = persistInfo.First.ToString().Split(';');
			string[] typeName = persistedString[1].Split(':');

			Control control = null;
			switch (typeName[0])
			{
				case "UC":
					string filename = typeName[1];
					if (File.Exists(Context.Server.MapPath(filename)))
						control = Page.LoadControl(filename);
					break;

				case "C":
					Type type = Type.GetType(typeName[1], true, true);
					control = (Control)Activator.CreateInstance(type);
					break;
			}

			if (control != null)
			{
				control.ID = persistedString[2];

				if (persistedString[0] == "C")
					parent.Controls.Add(control);

				if (ControlRestored != null)
					ControlRestored(control);

				foreach(Pair pair in (ArrayList)persistInfo.Second)
					RestoreChildStructure(pair, control);

			}
		}
		#endregion
	}
	#endregion
}

// --- EOF ---
